package com.hk.blog.service.impl;

import com.hk.blog.domain.Permission;
import com.hk.blog.domain.Role;
import com.hk.blog.domain.UserInfo;
import com.hk.blog.repository.jpa.UserInfoRepository;
import com.hk.blog.service.*;
import com.hk.commons.JsonResult;
import com.hk.commons.util.*;
import com.hk.core.authentication.security.SecurityUserPrincipal;
import com.hk.core.data.jpa.repository.BaseJpaRepository;
import com.hk.core.redis.locks.RedisLock;
import com.hk.core.service.exception.ServiceException;
import com.hk.core.service.jpa.impl.JpaServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service(value = "userInfoService")
@RequiredArgsConstructor
public class UserInfoServiceImpl extends JpaServiceImpl<UserInfo, Long> implements UserInfoService, UserDetailsService {

    private final UserInfoRepository userInfoRepository;

    private final PasswordEncoder passwordEncoder;

    private final RoleService roleService;

    private final UserRoleService userRoleService;

    private final PermissionService permissionService;

    private final RolePermissionService rolePermissionService;

    @Override
    protected ExampleMatcher ofExampleMatcher() {
        return super.ofExampleMatcher()
                .withIgnoreCase("password")
                .withMatcher("userType", ExampleMatcher.GenericPropertyMatchers.exact())
                .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.contains())
                .withMatcher("nickName", ExampleMatcher.GenericPropertyMatchers.contains());
    }

    @Override
    protected BaseJpaRepository<UserInfo, Long> getBaseRepository() {
        return userInfoRepository;
    }

    @Override
    public Optional<UserInfo> findByUsername(String username) {
        return StringUtils.isEmpty(username) ? Optional.empty() : userInfoRepository.findByUsername(username);
    }

    @Override
    @Transactional
    public UserInfo insert(UserInfo userInfo, Function<UserInfo, UserInfo> function) {
        UserInfo info = function.apply(userInfo);
        info.setId(null);
        Lock lock = new RedisLock("UserInfo");
        lock.lock();
        try {
            if (userInfoRepository.existByUsername(info.getUsername())) {
                throw new ServiceException(JsonResult.badRequest("用户账号已存在:" + userInfo.getUsername()));
            }
            if (userInfoRepository.existByMobileNumber(info.getMobileNumber())) {
                throw new ServiceException(JsonResult.badRequest("用户手机号已存在:" + userInfo.getMobileNumber()));
            }
            info.setPassword(passwordEncoder.encode(info.getPassword()));
            info.setUserStatus(ObjectUtils.defaultIfNull(info.getUserStatus(), ByteConstants.ONE));
            return userInfoRepository.save(info);
        } finally {
            lock.unlock();
        }

    }

    @Override
    public UserInfo updateById(UserInfo userInfo, Function<UserInfo, UserInfo> function) {
        UserInfo info = function.apply(userInfo);
        AssertUtils.isTrue(Objects.nonNull(info.getId()), "更新用户Id不能为空");
        Lock lock = new RedisLock("UserInfo");
        lock.lock();
        try {
            userInfoRepository.findByUsername(userInfo.getUsername()).ifPresent(find -> {
                if (!Objects.equals(find.getId(), userInfo.getId())) {
                    throw new ServiceException(JsonResult.badRequest("用户账号已存在:" + userInfo.getUsername()));
                }
            });
            userInfoRepository.findByMobileNumber(userInfo.getMobileNumber()).ifPresent(find -> {
                if (!Objects.equals(find.getId(), userInfo.getId())) {
                    throw new ServiceException(JsonResult.badRequest("用户手机号已存在:" + userInfo.getMobileNumber()));
                }
            });
            UserInfo dbUser = userInfoRepository.getById(info.getId());
            dbUser.setNickName(info.getNickName());
            dbUser.setEmail(info.getEmail());
            dbUser.setUserType(info.getUserType());
            dbUser.setUserStatus(info.getUserStatus());
            return userInfoRepository.save(dbUser);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        var userInfoOptional = userInfoRepository.findByUsername(username);
        if (userInfoOptional.isEmpty()) {
            throw new UsernameNotFoundException("用户不存在");
        }
        var userInfo = userInfoOptional.get();
        Set<Long> roleIds = userRoleService.findRoleIdByUserId(userInfo.getId());
        Set<String> roleNames = Collections.emptySet();
        Set<String> permsNames = Collections.emptySet();
        if (CollectionUtils.isNotEmpty(roleIds)) {
            List<Role> roles = roleService.findByIds(roleIds);
            roleNames = roles.stream()
                    .map(Role::getRoleCode)
                    .collect(Collectors.toSet());
            Set<Long> permissionIds = rolePermissionService.findPermissionIdsByRoleIds(roleIds);
            if (CollectionUtils.isNotEmpty(permissionIds)) {
                permsNames = permissionService.findByIds(permissionIds).stream()
                        .map(Permission::getPermissionCode)
                        .collect(Collectors.toSet());
            }
        }
        return new SecurityUserPrincipal(userInfo.getId(), null, null, null, null, username, userInfo.getNickName(),
                userInfo.getUserType(), userInfo.getMobileNumber(), userInfo.getEmail(), userInfo.getGender(), userInfo.getIconPath(),
                userInfo.getPassword(), userInfo.getUserStatus(), roleNames, permsNames);
    }
}
